/* Author: Ratish J. Punnoose, 2006
 * This file is part of TiROS, the Tickless Real-Time Operating System.
 * Copyright(c) 2006, 2007: Ratish J. Punnoose.
 * Copyright(c) 2006 Sandia Corporation. Under the terms of Contract
 * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
 * certain rights in this software. 
 * 
 * TiROS is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 or (at your option) any later version.
 *
 * TiROS is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with TiROS; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * As a special exception, if other files instantiate templates or use macros
 * or inline functions from this file, or you compile this file and link it
 * with other works to produce a work based on this file, this file does not
 * by itself cause the resulting work to be covered by the GNU General Public
 * License. However the source code for this file must still be made available
 * in accordance with section (3) of the GNU General Public License.
 *
 * This exception does not invalidate any other reasons why a work based on
 * this file might be covered by the GNU General Public License.
 *
 */


#include <setjmp.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/time.h>
#include "tr_port.h"
#include <tiros/tiros.h>
#include "../../tr_int.h"
#include <util.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>




#if (PORT_TIMER_TYPE == 1) || defined (__CYGWIN__)
#define ITIMER_TYPE 	ITIMER_REAL
#define CLOCK_TYPE 	CLOCK_REALTIME
#define SIGALRM_TYPE	SIGALRM
#elif PORT_TIMER_TYPE == 2
#define ITIMER_TYPE 	ITIMER_VIRTUAL
#define CLOCK_TYPE	CLOCK_PROCESS_CPUTIME_ID
#define SIGALRM_TYPE	SIGVTALRM
#else
#error "PORT_TIMER_TYPE should be set to 1 or 2"
#endif





void halint_setup_kernel_trap(void);
void halint_timer_setup(void);
void sig_handler_alarm(int sig);



#define POSIX_PORT_STK_INIT_WORD   0x3C576239

static osstkptr_t halint_alt_stk_allocate(osstkptr_t stk, osword_t stacksize)
{
	osstkptr_t allocated_stk;
#ifdef TIROS_STK_CHECK
	osword_t *posix_stk_ptr;
	int i;
#endif

	/* Initialize memory area */
	memset(stk, 0, sizeof(osword_t) * stacksize);

	/* The Posix port has a separate context storage space and
	   stack space. */
	/* 1. Allocate stack space */	
	allocated_stk = mmap(NULL, TIROS_POSIX_STKSZ,
			     PROT_READ|PROT_WRITE|PROT_EXEC, 
			     MAP_PRIVATE | MAP_ANON,
			     -1, 0);


	if (allocated_stk == ILLEGAL_STACK ) {
		TR_MSG_CRIT("Memory allocation for stk failed");
		exit(1);
	}

	if (stacksize < TRPORT_MIN_CTXT_SZ) {
		TR_MSG_CRIT("Stacksize too small");
		exit(1);
	}

#ifdef TIROS_STK_CHECK
	/* Save location of allocated stack on our regular stack 
	 * stk is the regular stack.
	 * Top of this stack is  stk + sizeof(osword_t)*(stacksize-1);	 
	 **/
	*(osword_t ** )(stk + (stacksize-1)) =  allocated_stk;

	posix_stk_ptr = allocated_stk;
	for(i=TIROS_POSIX_STKSZ/sizeof(osword_t); i; i--)
		*posix_stk_ptr++ = POSIX_PORT_STK_INIT_WORD;
#endif

	return allocated_stk;
}




void halint_timer_setup(void)
{
	struct sigaction  alarm_setup;
#ifdef __CYGWIN__
	trtime_t wake_time;
#endif

	sigset_t blockmask;
	sigemptyset( &blockmask);
	sigaddset(&blockmask, SIGALRM_TYPE );

	alarm_setup.sa_handler = sig_handler_alarm;
	alarm_setup.sa_mask   = blockmask;
	alarm_setup.sa_flags  = 0;

	/* Setup alarm handler */
	sigaction(SIGALRM_TYPE, &alarm_setup, NULL);
	
#ifdef __CYGWIN__	
	/* Cygwin has an odd bug in our usage of it.  If an alarm has
	 * never been set  before a sleep (wait) is encountered, a
	 * crash can occur upon some invocations of sleep.   This can
	 * be bypassed by setting the alarm once. It doesn't
	 * even matter that the alarm set is invalid.  So thats what
	 *	 we do. 
	 * The sequence of events that seems to cause a crash is as
	 * follows: 
	 * 1. Setup alarm handler
	 * 2. context switch.
	 * 3. perform sleep.
	 * If a sleep is performed before the context switch, all is fine.
	 */
	
	wake_time.units = ~0;
	wake_time.units = 1;
	hal_alarm_set(&wake_time);
	
#endif

}

void hal_time_get(trtime_t *t)
{
	struct timespec curr_time;
	clock_gettime(CLOCK_TYPE, &curr_time);
	t->units = curr_time.tv_sec;
	/* Subunits is used with tv_nsec */
	t->subunits = curr_time.tv_nsec;
}

void sigint_handler(int sig)
{
	exit(0);
}

void sigint_setup(void)
{
	struct sigaction sigint_setup;
	sigset_t blockmask;
	sigemptyset(&blockmask);
	sigint_setup.sa_handler = sigint_handler;
	sigint_setup.sa_mask = blockmask;
	sigint_setup.sa_flags = 0;
	sigaction(SIGINT, &sigint_setup, NULL);
}

int hal_time_set(const trtime_t *t)
{
	return 0;
}


void  hal_alarm_set(const trtime_t *lt) 
{ 
	/* The alarm function takes the difference between the current
	   time and the wakeup time */
	struct itimerval t;
	trtime_t curr_time, sleepinterval;
	trtime_t infinite_timeout = {~0, ~0};
	int rc;

	TR_MSG_TRACE("In hal_alarm_set\n");


	t.it_interval.tv_sec = 0;
	t.it_interval.tv_usec = 0;
	if (!lt) {
		return;
	} else if (time_compare(lt, &infinite_timeout) == 0) {
		return;
	} else {
		hal_time_get(&curr_time);
		time_sub(lt, &curr_time, &sleepinterval);

		TR_MSG_INFO("curr:"); TR_TIME_INFO(&curr_time);
		TR_MSG_INFO("lt:");   TR_TIME_INFO(lt);
		TR_MSG_INFO("wake at "); 
		TR_TIME_INFO(&sleepinterval);

		t.it_value.tv_sec = sleepinterval.units;
		/* Subunits is in nsec, so covert to usec */
		t.it_value.tv_usec = sleepinterval.subunits / 1000;
				
	}
	
	
	rc = setitimer( ITIMER_TYPE, &t, 0);
	if (rc < 0) {
		if (errno == EFAULT)
			TR_MSG_WARN("setitimer:EFAULT\n");
		else if (errno == EINVAL)
			TR_MSG_WARN("setitimer:EINVAL\n");
		else if (errno == ENOSYS)
			TR_MSG_WARN("setitimer:ENOSYS\n");
		else {
			TR_MSG_WARN("setitimer error");
		}
		
		
	}

	TR_MSG_TRACE("Done hal_alarm_set\n");


}





int halint_kernel_trap_flagged;



void hal_init(void)
{
	halint_timer_setup();
	sigint_setup();
}










#ifdef __CYGWIN__
#define CONTEXT_FLAGS CONTEXT_FULL




void hal_ctxt_load(osstkptr_t ctxt_ptr)
{

	ctxt_t *ctxt = (ctxt_t *) ctxt_ptr;
	sigset_t *signal_state_addr;

	/* Load the new signal mask */
	signal_state_addr = (sigset_t*)( (void*)ctxt_ptr) + sizeof(ctxt_t);
	sigprocmask(SIG_SETMASK, signal_state_addr, 0);

	SetThreadContext( GetCurrentThread() , ctxt);
	return;
}


osstkptr_t hal_ctxt_switch(void)
{
	ctxt_t * ctxt;
	osstkptr_t ctxt_ptr;
	HANDLE thread_handle;
	sigset_t *signal_state_addr;

	ctxt_ptr = osint_running_task_ctxt();
	ctxt = (ctxt_t*)ctxt_ptr;
	thread_handle = GetCurrentThread();

	/* Check to see if the current task has been deleted */
	if (ctxt_ptr != ILLEGAL_STACK) {
		/* Store the context for the current thread */
		GetThreadContext(thread_handle, ctxt);

		/* This method of storing context does not store the state of
		   the signal mask.  We store that explicitly, beyond where
		   the Thread Context is saved. */
		signal_state_addr = (sigset_t*)( (void*)ctxt_ptr) + sizeof(ctxt_t);
		sigprocmask(SIG_SETMASK, 0, signal_state_addr);

		ctxt_ptr = osint_taskswitcher(ctxt_ptr);
		/* Load the new signal mask */
		signal_state_addr = (sigset_t*)( (void*)ctxt_ptr) + sizeof(ctxt_t);
		sigprocmask(SIG_SETMASK, signal_state_addr, 0);

		ctxt = (ctxt_t*)ctxt_ptr;
		/* Load context for the new thread */
		SetThreadContext(thread_handle, ctxt);
	}else {
		ctxt_ptr = osint_taskswitcher(ctxt_ptr);
		/* Load the new signal mask */
		signal_state_addr = (sigset_t*)( (void*)ctxt_ptr) + sizeof(ctxt_t);
		sigprocmask(SIG_SETMASK, signal_state_addr, 0);

		ctxt = (ctxt_t*)ctxt_ptr;
		/* Load context for the new thread */
		SetThreadContext(thread_handle, ctxt);

	}

	return 0;
}



osstkptr_t hal_stk_init(osword_t *stk, osword_t stacksize, 
			osfnptr_t pc, osptr_t init_arg) 
{
	osstkptr_t allocated_stk;
	unsigned char * stkargs;
	ctxt_t * ctxt = (ctxt_t *) stk;
	sigset_t *signal_state_addr;
	int rc = 0;

	allocated_stk = halint_alt_stk_allocate(stk, stacksize);


	signal_state_addr = (sigset_t*)( (void*)stk) + sizeof(ctxt_t);
	sigemptyset(signal_state_addr);

	/* Create a valid context.  Start with our current context */

	ctxt->ContextFlags = CONTEXT_FLAGS;
	rc = GetThreadContext( GetCurrentThread(), ctxt);
	if (rc == 0) {
		TR_MSG_CRIT("Get current context failed");
		exit(1);
	}
	

	stkargs = ((unsigned char *)allocated_stk) + TIROS_POSIX_STKSZ;
	stkargs -= 8;

	ctxt->Eip = (unsigned long) pc;
	ctxt->Esp = (unsigned long) stkargs - 4;	
	ctxt->ContextFlags = CONTEXT_FLAGS;
	
	*((osptr_t *)stkargs) = init_arg;	
	
	return stk;

}



#ifdef TIROSINT_DATA_DEBUG
/** Compute the program counter, from the stored context (stack
 * pointer)
 * This function is used for debugging.  Implement this by extracting
 * the program counter out of the stack pointer if possible.
 *
 * @param ctxt_ptr  The pointer to the context of the task.
 * @return The program counter */
osptr_t hal_pc_from_ctxt(osstkptr_t ctxt_ptr)
{
	/* Windows only runs on one hardware platform */
	ctxt_t * ctxt;

	ctxt = (ctxt_t*) ctxt_ptr;
	/* Return the instruction pointer */
	return (osptr_t)ctxt->Eip;
}


#ifdef TIROS_STK_CHECK
osword_t hal_stkusage_curr(osstkptr_t ctxt_ptr, osstkptr_t base_stkptr,
			   osword_t stksize)
{
	osword_t stk_usage;
	osword_t *esp;
	ctxt_t *ctxt = (ctxt_t*)ctxt_ptr;
	osword_t *base_allocated_stk;

	esp = (osword_t*)ctxt->Esp;  /* x86 stack pointer */

	base_allocated_stk = 
		*(osword_t**  )(base_stkptr + (stksize-1));


	/* Esp should be higher than base_allocated_stk, else overflow
	*/
	if (esp < base_allocated_stk)
		stk_usage = (osword_t)-1;
	else {
		stk_usage = (base_allocated_stk +
			     TIROS_POSIX_STKSZ/sizeof(osword_t)  -
			     esp);
	}
	return stk_usage;
}
#endif



#endif




#else  /* Standard Unix */





/** Context switch from user level (non-ISR)
 *  This function is called from within an OS call, to switch the
 *  running process.  It also provides a return value. This is a
 *  pseudo return-value.  This function does not actually determine
 *  the value returned.  The return value is set by hal_retval_set
 *  which is often invoked when waking the process up from a blocked
 *  state. 
 * @return  Return value.  */
osstkptr_t hal_ctxt_switch(void)
{
	ctxt_t *new_ctxt;
	ctxt_t *old_ctxt;
        /* This function is carefully crafted.  Be careful, making any
         * changes to this */  

	osstkptr_t ctxt_ptr = osint_running_task_ctxt();
	old_ctxt = (ctxt_t*) ctxt_ptr;


	/* Check to see if current task has been deleted. If so, don't
	   save the context */
	if (ctxt_ptr != ILLEGAL_STACK) {
		ctxt_ptr = osint_taskswitcher(ctxt_ptr);
		new_ctxt = (ctxt_t *)ctxt_ptr;
		swapcontext(old_ctxt, new_ctxt);
	} else {
		ctxt_ptr = osint_taskswitcher(ctxt_ptr);
		new_ctxt = (ctxt_t *)ctxt_ptr;
		setcontext(new_ctxt);
	}

        /* This is just to eliminate compiler warnings.  In theory,
	 * program execution flow never reaches this point because
	 * hal_ctxt_load performs a return from interrupt.  This
	 * tricks the calling process into looking at the return value
	 * in one of its registers.  This return value would have been
	 * set earlier by hal_retval_set. */
        return 0; 
}




osstkptr_t hal_stk_init(osword_t *stk, osword_t stacksize, 
			osfnptr_t pc, osptr_t init_arg) 
{
	osstkptr_t allocated_stk;
	ctxt_t *ctxt = (ctxt_t*) stk;
	int rc;

	allocated_stk = halint_alt_stk_allocate(stk, stacksize);

	/* Create a valid context.  Start with our current context */
	rc = getcontext(ctxt);
	if (rc != 0) {
		TR_MSG_CRIT("Get current context failed");
		exit(1);
	}
	

	/* Setup parameters for our context */
	sigemptyset(&ctxt->uc_sigmask);            /* No signals blocked */
	ctxt->uc_stack.ss_sp = allocated_stk;       /* Set stack */
	ctxt->uc_stack.ss_size = TIROS_POSIX_STKSZ;
	ctxt->uc_link = NULL;                       /* No next context */

	/* Make an execution context.  The second argument to make
	 * context is the task start function. The third argument is
	 * the number of subsequent arguments. 
	 * We pass  
	 * 1:  The function pointer where task execution should start.
	 * 2:  The intial argument to be passed to the task.
	*/
	makecontext(ctxt, (void (*)(void))pc, 1, init_arg);  

	return stk;
}




void hal_ctxt_load(osstkptr_t ctxt_ptr)
{
	ctxt_t *ctxt = (ctxt_t*) ctxt_ptr;
	TR_MSG_TRACE("hal_ctxt_load\n");
	setcontext(ctxt);
	TR_MSG_CRIT("hal_ctxt_load failed at setcontext");
	return;
}



#ifdef TIROSINT_DATA_DEBUG
/** Compute the program counter, from the stored context (stack
 * pointer)
 * This function is used for debugging.  Implement this by extracting
 * the program counter out of the stack pointer if possible.
 *
 * @param ctxt_ptr  The pointer to the context of the task. 
 * @return The program counter */
osptr_t hal_pc_from_ctxt(osstkptr_t ctxt_ptr)
{
	/* In the case of Posix, it is hard to determine the program
	 * counter since there are many posix hardware platforms and
	 * the program counter is hardware specific */
	return (osptr_t)TR_ILLEGAL_ADDR;

}

#ifdef TIROS_STK_CHECK
osword_t hal_stkusage_curr(osstkptr_t ctxt_ptr, osstkptr_t base_stkptr,
			   osword_t stksize)
{
	return 0;
}
#endif /* TIROS_STK_CHECK */

#endif /* TIROSINT_DATA_DEBUG */


#endif /* Standard Unix */



/** Signal wakeup upon alarm */
void sig_handler_alarm(int sig)
{
	trtime_t curr_time;
	halint_kernel_trap_flagged = 0;

	hal_time_get(&curr_time);
	TR_MSG_TRACE("SH: ALRM\n");

	if (  osint_alarm_reached(&curr_time) )
		OS_KERNEL_TRAP();	

	if (halint_kernel_trap_flagged) {
		hal_ctxt_switch();
	}

	
}


#ifdef TIROSINT_DATA_DEBUG
#ifdef TIROS_STK_CHECK
osword_t hal_stkusage_max(osstkptr_t ctxt_ptr, osstkptr_t base_stkptr,
			  osword_t stksize)
{
	osword_t *base_allocated_stk;
	osword_t rc;

	base_allocated_stk = 
		*(osword_t**  )(base_stkptr + (stksize-1));

	/* Measure the amount of free stack */
	for (rc = 0; rc < TIROS_POSIX_STKSZ/sizeof(osword_t); rc++) {
		/* If the stack word is not the same as what we wrote
		   initially, it has been modified, and thus used */
		if ( *base_allocated_stk != POSIX_PORT_STK_INIT_WORD)
			break;

		base_allocated_stk++;
	}

	/* Compute the used stack */
	return TIROS_POSIX_STKSZ/sizeof(osword_t) - rc;

}

#endif

#endif
